<!DOCTYPE html>
<html>
  <head>
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"
    />
    <meta charset="UTF-8" />
    <title>Swagger UI</title>
    <link
      rel="icon"
      type="image/png"
      href="./images/favicon-16x16.png"
      sizes="16x16"
    />
    <link
      rel="icon"
      type="image/png"
      href="./images/favicon-32x32.png"
      sizes="32x32"
    />
    <link href="./style/css.css" rel="stylesheet" />
    <link href="./style/materialdesignicons.min.css" rel="stylesheet" />
    <link href="./style/vuetify.min.css" rel="stylesheet" />

    <style>
      html {
        overflow-y: auto;
      }

      .v-application a {
        color: #1976d2;
        word-break: break-all;
        white-space: normal;
      }

      .break-word {
        word-break: break-all;
        white-space: normal;
      }

      .api-toc {
        list-style-type: none !important;
        margin: 0;
        padding: 32px 0 0;
        text-align: left;
        width: 100%;
      }

      .api-toc li {
        border-left: 2px solid transparent;
        padding: 0 24px 0 8px;
      }

      .api-toc li a {
        color: inherit;
        font-size: 0.875rem;
        font-weight: 400;
        text-decoration: none;
        transition: color 0.1s ease-in;
      }

      .code {
        font-family: Consolas, 'Courier New', monospace;
      }

      .no-hover:hover {
        background: none !important;
      }

      .pointer {
        cursor: pointer;
      }

      .ds-suggestion-item.actived,
      .ds-suggestion-item:hover {
        background: rgba(69, 142, 225, 0.05);
        cursor: pointer;
      }

      .overflow-hidden {
        overflow: hidden !important;
      }

      .toast.small > div {
        min-width: initial !important;
        min-height: initial !important;
      }

      .toast.small > div > div {
        line-height: initial !important;
        padding: 4px !important;
      }
    </style>
  </head>

  <body>
    <div id="app">
      <v-app>
        <!-- 左侧 -->
        <v-navigation-drawer
          app
          permanent
          style="
            height: 100vh;
            top: 64px;
            transform: translateX(0%);
            width: 300px;
            max-height: calc(100% - 64px);
          "
        >
          <template v-slot:prepend>
            <app-api-description
              v-if="curProject"
              :info="curProject.info"
              :projects="projects"
              @select="handleSelectProject"
            ></app-api-description>
            <app-api-import
              :by-url="false"
              by-local
              @success="parseProject"
            ></app-api-import>
            <v-divider></v-divider>
            <v-text-field
              v-model="searchNamespace"
              prepend-inner-icon="mdi-magnify"
              placeholder="搜索"
              dense
              flat
              hide-details
              solo
            ></v-text-field>
            <v-divider></v-divider>
          </template>
          <app-api-namespace
            v-model="index.namespace"
            :namespaces="curNamespaces"
            @select="handleSelect"
          ></app-api-namespace>
          <template v-slot:append>
            <v-divider></v-divider>
            <v-row justify="center" no-gutters>
              <v-col class="py-4 text-center" cols="12">
                © {{ new Date().getFullYear() }} ecuplxd
              </v-col>
            </v-row>
          </template>
        </v-navigation-drawer>
        <!-- 右侧 -->
        <app-api-toc
          v-model="index.api"
          :namespace="curNamespace"
        ></app-api-toc>
        <!-- 头部 -->
        <v-app-bar
          app
          clipped-left
          clipped-right
          flat
          outlined
          style="
            height: 64px;
            margin-top: 0px;
            transform: translateY(0px);
            left: 0px;
            right: 0px;
            z-index: 7;
          "
        >
          <img src="./images/logo.svg" height="64px" style="margin-top: 7px" />
          <v-toolbar-title>Swagger UI</v-toolbar-title>
          <app-api-import @success="parseProject"></app-api-import>
          <v-spacer></v-spacer>
          <app-api-search
            :namespaces="curNamespaces"
            @select="handleSelect"
          ></app-api-search>
          <v-spacer></v-spacer>
        </v-app-bar>
        <!-- 内容区 -->
        <v-main style="padding: 64px 256px 0px 300px">
          <v-container fluid>
            <v-snackbar
              v-model="copySuccess"
              top
              color="#66BB6A"
              :timeout="1500"
              class="toast small"
            >
              <v-icon class="mx-2" small>mdi-check</v-icon>复制成功
            </v-snackbar>
            <app-api-items
              :apis="curNamespace && curNamespace.apis"
              :actived-index="index.api"
              :force-select="forceSelect"
              @index-change="handleApiIndexChange($event)"
            ></app-api-items>
          </v-container>
        </v-main>
      </v-app>
    </div>

    <script src="./utils/index.js"></script>
    <script src="./lib/vue.js"></script>
    <script>
      Vue.mixin({
        data: function () {
          return {
            colors: {
              POST: '#49cc90',
              GET: '#61affe',
              PUT: '#fca130',
              DELETE: '#f93e3e',
              PATCH: '#50e3c2',
            },
            TYPE_MAP: {
              string: 'string',
              integer: 'number',
              file: 'File',
              boolean: 'boolean',
              object: 'Object',
              array: '[]',
            },
          };
        },
        methods: {
          genID() {
            return Math.random().toString(36).substr(2);
          },
          listenerKey(code, cb) {
            this.$nextTick(() =>
              document.addEventListener('keyup', (e) => {
                e.keyCode === code && cb.call(this);
              })
            );
          },
        },
      });
    </script>
    <script src="./lib/vuetify.js"></script>
    <script src="./components/copy.js"></script>
    <script src="./components/type.js"></script>
    <script src="./components/search.js"></script>
    <script src="./components/namespace.js"></script>
    <script src="./components/description.js"></script>
    <script src="./components/toc.js"></script>
    <script src="./components/apiItem.js"></script>
    <script src="./components/apiItems.js"></script>
    <script src="./components/response.js"></script>
    <script src="./components/import.js"></script>
    <script>
      new Vue({
        el: '#app',
        vuetify: new Vuetify(),
        data: {
          tocs: [],
          index: {
            project: 0,
            namespace: -1,
            api: -1,
          },
          namespacesMap: {},
          types: {},
          searchNamespace: '',
          copySuccess: false,
          projects: [],
          forceSelect: +new Date(),
        },
        computed: {
          curProject() {
            return this.projects[this.index.project] || {};
          },
          curNamespaces() {
            return this.curProject.namespaces || [];
          },
          curNamespace() {
            return this.curNamespaces[this.index.namespace];
          },
        },
        watch: {
          index: {
            namespace(namespace) {
              this.setIndex(this.index.project, namespace, this.index.api);
            },
            api(api) {
              this.setIndex(this.index.project, this.index.namespace, api);
            },
          },
          searchNamespace() {
            this.filterNamespace();
          },
        },
        methods: {
          setIndex(project, namespace, api) {
            sessionStorage.setItem('project', project);
            sessionStorage.setItem('activedIndex', namespace);
            sessionStorage.setItem('activedAPIIndex', api);
          },
          handleApiIndexChange(index) {
            if (index !== this.index.api) {
              // this.activedAPIIndex = index;
            }
            this.setIndex(this.index.namespace, index);
          },
          restoreIndex() {
            this.index.namespace = +sessionStorage.getItem('activedIndex');
            this.index.api = +sessionStorage.getItem('activedAPIIndex');
            return this;
          },
          filterNamespace() {
            this.curNamespaces.forEach((namespace) => {
              namespace.matched =
                namespace.name.includes(this.searchNamespace) ||
                namespace.description.includes(this.searchNamespace);
            });
          },
          loadProjects() {
            fetch('./assets/swagger.json')
              .then((res) => res.json())
              .then((config) => {
                this.parseProject(config);
              });
          },
          parseProject(project) {
            console.log(project);

            if (this.setProjects(project)) {
              return this;
            }

            this.getProjectInfo(project)
              .getNamespace(project)
              .getNamespaceApis(project)
              .getTypes(project.definitions)
              .restoreIndex(this.projects.length, 0, 0);

            return this;
          },
          setProjects(project) {
            const { title, version } = project.info;
            const id = encodeURIComponent(title + '|' + version);

            if (this.projects.find((project) => project.id === id)) {
              alert(`${title} ${version} 已存在`);
              return true;
            }

            project.id = id;
            project.info.id = id;
            this.projects.push(project);
            sessionStorage.setItem('PROJECTS', JSON.stringify(this.projects));
            return false;
          },
          getProjectInfo(project) {
            project.info.host = project.host;
            project.info.basePath = project.basePath;
            return this;
          },
          getNamespace(project) {
            this.namespacesMap = {};
            project.namespaces = project.tags.map((tag, index) => {
              tag.apis = [];
              tag.matched = true;
              this.namespacesMap[tag.name] = index;
              return tag;
            });
            return this;
          },
          transformParameters(parameters = []) {
            parameters.forEach((parameter) => {
              let required = parameter.required ? '' : '?';
              parameter.display = parameter.name + required;
            });
          },
          iterObj(obj, cb) {
            return Object.keys(obj)
              .sort()
              .map((key) => cb.call(null, key, obj[key]));
          },
          getNamespaceApis(project) {
            let apis = [];
            this.iterObj(project.paths, (url, methods) => {
              this.iterObj(methods, (method, config) => {
                config = {
                  ...config,
                  produce: config.produces && config.produces[0],
                  url: '`' + url.replace(/\{/gi, '${') + '`',
                  rawUrl: url,
                  method: method.toUpperCase(),
                  method_: method,
                  description: config.summary,
                };
                apis.push(config);
              });
            });

            apis.forEach((api) => {
              this.transformParameters(api.parameters);
              api.tags.forEach((tag) => {
                const index = this.namespacesMap[tag];
                project.namespaces[index].apis.push(api);
              });
            });

            return this;
          },
          getTypes(definitions) {
            let types = {};
            for (const definition in definitions) {
              if (definitions.hasOwnProperty(definition)) {
                const config = definitions[definition];
                types[definition] = {
                  __required: {},
                  __example: {},
                  __refTypes: [],
                };

                config.required &&
                  config.required.map((item) => {
                    types[definition].__required[item] = true;
                  });

                for (const filed in config.properties) {
                  if (config.properties.hasOwnProperty(filed)) {
                    const filedConfig = config.properties[filed];
                    const type = new TypeHelper(filedConfig);
                    types[definition][filed] = type.type;
                    types[definition].__example[filed] =
                      filedConfig.example || type.type;
                    if (type.refType) {
                      types[definition].__refTypes.push(
                        type.type.replace('[]', '')
                      );
                    }
                  }
                }
              }
            }

            this.types = types;
            console.log(this.types);
            return this;
          },
          handleSelect(namespace, api, forceSelect) {
            forceSelect = forceSelect || false;
            this.index.namespace = namespace;
            this.index.api = api;
            if (forceSelect) {
              this.forceSelect = +new Date();
            }
          },
          handleSelectProject(project) {
            this.setIndex(project, 0, 0);
            this.index.project = project;
            this.index.namespace = 0;
            this.index.api = 0;
          },
        },
        mounted() {
          this.loadProjects();
        },
      });
    </script>
  </body>
</html>
